狀態機不該是 if/else 的迷宮,它的本質是一張規則流程表。
把轉移規則寫成表,讀表執行就好。
先釐清「狀態」與「動作」
建模步驟
// 🔴 臭味道:這不是狀態機,這是一個 bug 產生器
function nextStatus(status, action) {
if (status === 'created') {
if (action === 'pay') return 'paid';
if (action === 'cancel') return 'canceled';
} else if (status === 'paid') {
if (action === 'ship') return 'shipped';
// 如果這裡要加一個 'refund' 動作呢?繼續加 if?
}
// 如果要加一個 'shipped' 狀態呢?再加一個 else if?
return status;
}
規則被寫死在程式碼裡: 這些 if
判斷就是業務規則。
return ...
)混在一起,無法輕易地被檢視、修改或理解。維護性為零: 每增加一個狀態或動作,都必須修改程式碼的控制流程。
它在說謊: 這段程式碼的複雜度隱藏了它背後簡單的真相:它只是一張二維表格的查詢。
現在把規則從程式碼裡解放出來,讓它回歸其本質——資料。
// 🟢 好味道:規則就是規則,程式碼就是程式碼
// 這就是你的狀態機。它是一份設定檔,不是一段程式。
// 你可以把它印出來貼在牆上,任何人都能看懂。
const transitions = {
created: { pay: 'paid', cancel: 'canceled' },
paid: { ship: 'shipped', refund: 'refunded' },
shipped: { complete: 'completed' },
// 新增狀態或動作,只需要在這裡加一行資料。
};
// 這段程式碼變得極其單純穩定。它不需要知道任何業務規則。
// 它的唯一工作就是查表。這段程式碼可能永遠都不需要再改了。
function nextStatus(status, action) {
return transitions[status]?.[action] ?? status;
}
資料與程式碼分離: transitions
物件是「什麼」(What),nextStatus
函式是「如何」(How)。
可讀且可擴展: 任何人,甚至非開發者,都能讀懂 transitions
表。
好程式碼的體現: 這就是我說的「消除特殊情況」。
if-else
版本裡,每個 if
都是一個特殊情況。在查表法裡,沒有特殊情況,所有規則都只是表裡的一行資料。絕對不要把副作用(寄信、寫資料庫)跟狀態轉移的純邏輯混在一起。
正確的做法是兩步:
計算新狀態(純函式): const newState = nextStatus(oldState, action);
這一步不應該有任何副作用。
觸發副作用(如果不純): if (newState !== oldState) { triggerSideEffects(oldState, newState, action); }
把這兩者分開,你可以獨立測試你的狀態轉移邏輯,而不需要真的去寄一封 email。
在下次準備寫一長串 if-else
或 switch
之前,停下來問自己:
Code is complicated. Data is simple.
把你的複雜性放進資料結構裡,用查表法讓狀態清晰可控,然後讓你的程式碼變優雅且簡單。
這才是真正的專業除臭。